using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Net; 
using System.IO;
using System.Diagnostics;
using Rest;
using Rest.Transport;
 
namespace Coriander.Google.Charting
{
    /// <summary>
    /// Builds google charts
    /// </summary>
    public class ChartBuilder
    {
        #region Fields, properties and ctors

        String serviceUrl = "http://chart.apis.google.com/chart";

        ChartFill chartFill; 

        String commandText;

        ChartGridLines grid = new ChartGridLines(10, 20, 5, 1);

        ChartLineStyle lineStyle = ChartLineStyle.Normal;

        IList<ChartMarker> markers;

        IList<ChartRangeMarker> rangeMarkers;

        /// <summary>
        /// A ChartFill instance
        /// </summary>
        public ChartFill ChartFill
        {
            get { return chartFill; }
            set { chartFill = value; }
        }

        /// <summary>
        /// The line style
        /// </summary>
        public ChartLineStyle LineStyle
        {
            get { return lineStyle; }
            set { lineStyle = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public IList<ChartRangeMarker> RangeMarkers
        {
            get { return rangeMarkers ?? (rangeMarkers = new List<ChartRangeMarker>()); }
        }

        /// <summary>
        /// 
        /// </summary>
        public IList<ChartMarker> Markers
        {
            get { return markers ?? (markers = new List<ChartMarker>()); }
        }

        public const Double MaxChartArea = 3e5;

        /// <summary>
        /// 
        /// </summary>
        public ChartGridLines Grid
        {
            get { return grid; }
            set { grid = value; }
        }

        /// <summary>
        /// 
        /// </summary>
        public String CommandText
        {
            get { return commandText; }
        }

        /// <summary>
        /// Gets or sets the service URL
        /// </summary>
        public String ServiceUrl
        {
            get { return serviceUrl; }
            set { serviceUrl = value; }
        }

        #endregion

        /// <summary>
        /// Given a set of ChartAxis objects, create a set of RestParameters.
        /// </summary>
        /// <remarks>
        /// TODO: Do we have to do these all at once?
        /// 
        /// See: http://code.google.com/apis/chart/#axes_label_positions
        /// 
        /// axis type  :            chxt=x,y,r,x
        /// 
        /// axis labels:            chxl=
        ///                         0:|Jan|July|Jan|July|Jan|
        ///                         1:|0|100|
        ///                         2:|A|B|C|
        ///                         3:|2005|2006|2007
        /// 
        /// axis range:             chxr={axis index},{start of range},{end of range}|
        /// 
        /// axis label positions:   chxp=1,10,35,75|2,0,1,2,4
        /// 
        /// axis label style:       chxs={axis index},{color},{font size},{alignment}|
        /// </remarks>
        /// <param name="axes"></param>
        /// <returns></returns>
        static RestParameterCollection CreateLabels(
            IList<ChartAxis> axes)
        {
            StringBuilder axisLabelBuilder = new StringBuilder();
            StringBuilder axisRangeBuilder = new StringBuilder();
            StringBuilder axisBuilder      = new StringBuilder();
            StringBuilder axisStyleBuilder = new StringBuilder();
            RestParameterCollection result = new RestParameterCollection();

            // TODO: If the axis has a Name supplied, add an extra axis and put the
            // name on as a label.
            String axisStyle = null;

            for (Int32 i = 0; i < axes.Count; i++)
            {
                // Axis style
                if (axes[i].AxisStyle != null)
                {
                    axisStyleBuilder.AppendFormat(
                        "{0}{1},{2},{3},{4}",
                        axisStyleBuilder.Length > 0  ? "|" :  String.Empty, 
                        i,
                        ChartColor.ToHtml(axes[i].AxisStyle.Color, true),
                        axes[i].AxisStyle.FontSize,
                        axes[i].AxisStyle.Alignment
                    );      
                }

                // Axes
                axisBuilder.AppendFormat("{0}{1}",
                    axes[i].AxisType.Value,
                    i < axes.Count - 1 ? "," : String.Empty);

                // Add the labels
                if (axes[i].Labels.Count > 0)
                {
                    axisLabelBuilder.AppendFormat("{0}:|", i);

                    foreach (ChartAxisLabel label in axes[i].Labels)
                    {
                        axisLabelBuilder.AppendFormat("{0}|", label.Text);
                    }
                }

                String labelText = null;

                // Axis range
                axisRangeBuilder.Append(
                    labelText = String.Format("{0},{1},{2}{3}",
                    i,
                    axes[i].RangeLowerBound,
                    axes[i].RangeUpperBound,
                    i < axes.Count - 1 ? "|" : String.Empty)
                );

                Debug.WriteLine(String.Format("labeltext: {0}", labelText));
            }

            // Axes
            result.Add("chxt", axisBuilder.ToString());

            // Axis range
            //cmd.Parameters.Add("chxr", String.Format("2,0,{0}", yAxisRange));
            result.Add("chxr", axisRangeBuilder.ToString());

            // Labels
            //cmd.Parameters.Add("chxl", "0:|Jan|Feb|Mar|Apr|May|June|July|Aug|Sep|Oct|Nov|Dec|1:||days in year");
            result.Add("chxl", axisLabelBuilder.ToString());

            // Axis label positions
            // cmd.Parameters.Add("chxp", "0,100");
            result.Add("chxp", GetLabelPositions(axes));

            // Axis style
            if (axisStyleBuilder.Length > 0)
            {
                result.Add("chxs", axisStyleBuilder.ToString());  
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <remarks>
        ///  <axis index>,<label 1 position>,<label n position>|
        /// </remarks>
        /// <returns></returns>
        static String GetLabelPositions(IList<ChartAxis> axes)
        {
            StringBuilder buffer = new StringBuilder();

            // For the supplied axis, get the label postion attribute
            for (Int32 i = 0; i < axes.Count; i++)
            {
                buffer.AppendFormat("{0}", i);

                foreach (ChartAxisLabel label in axes[i].Labels)
                {
                    if (label.Position != null)
                    {
                        buffer.AppendFormat(",{0}",
                            label.Position != null ?
                            label.Position.Value.ToString() : String.Empty);
                    }
                }

                if (i < axes.Count - 1)
                {
                    buffer.Append("|");
                }
            }

            return buffer.ToString();
        }

        /// <summary>
        /// Ensures the supplied size is within allowed parameters.
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        Boolean ValidateSize(Size size)
        {
            return size.Width * size.Height <= MaxChartArea;  
        }

        /// <summary>
        /// TODO, 2007-12-09, BJB : Begin refactoring by abstracting the name/value pairs.
        /// Then this class just assembles them.
        /// 
        /// TODO, 2007-12-09, BJB : Perhaps each attribute knows how to serialize itself.
        /// An example is background - different parameters required to obtains
        /// gradient to solid fill, but they use the same parameter name. MAybe these are two
        /// implementations of a background parameter.
        /// </summary>
        /// <param name="size"></param>
        /// <param name="title"></param>
        /// <param name="axes"></param>
        /// <param name="color"></param>
        /// <param name="type"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public Image GetChart(
            Size size,
            String title,
            IList<ChartAxis> axes,
            Color color,
            ChartType type,
            Double[] data)
        {

            if (false == ValidateSize(size))
            {
                throw new InvalidOperationException(
                    String.Format("Invalid size: {0}. Area cannot exceed {1} pixels.", 
                    size,
                    MaxChartArea)
                ); 
            }

            RestCommand cmd = new RestCommand(ServiceUrl);
            GetRestTransport transport = RestTransport.Get;

            cmd.Transport = transport;

            // Size
            cmd.Parameters.Add("chs", String.Format("{0}x{1}", size.Width, size.Height));

            // Type : line chart (lc), 3D pie chart (p3)
            cmd.Parameters.Add("cht", type.Value);

            // Chart background color
            //cmd.Parameters.Add("chf", String.Format("c,s,{0}",
            //    ChartColor.ToHtml(BackgroundColor, true)));  
            if (chartFill != null)
            {
                cmd.Parameters.Add(ChartFill);
            }

            // [!] Or: Chart background gradient
            // cmd.Parameters.Add("chf", "c,lg,90,d0d0d0,0,ffffff,0.75");

            // Coloring bars
            // [!] supports multiple data sets. Comma delimited
            // chco=00ff00,f0f0f0
            cmd.Parameters.Add(
                "chco", 
                String.Format("{0}", 
                ColorTranslator.ToHtml(color).TrimStart('#')));

            // Title
            cmd.Parameters.Add("chtt", title);

            //
            // Add the axis labels
            //
            cmd.Parameters.AddRange(CreateLabels(axes));

            // Grid:
            cmd.Parameters.Add("chg", 
                String.Format("{0},{1},{2},{3}", 
                    grid.XAxisStepSize, 
                    grid.YAxisStepSize,
                    grid.LineSegmentLength,
                    grid.BlankSegmentLength
               )
            );

            //
            // Data
            //
            // data type: s (char), t (numeric)
            String[] stringdata = Array.ConvertAll<Double, String>(
                data,
                delegate(Double from)
                { 
                    return from.ToString(); 
                }
            );

            cmd.Parameters.Add("chd", String.Format("t:{0}", String.Join(",", stringdata)));

            //
            // Markers
            //
            if (Markers.Count > 0)
            {
                String temp = "c,FF0000,0,1.0,20.0"; 

                StringBuilder buffer = new StringBuilder();

                ChartMarker marker = null;

                for (Int32 i = 0; i < Markers.Count; i++)
                {
                    marker = Markers[i];

                    buffer.AppendFormat(
                        "{0},{1},{2},{3},{4}{5}",
                        marker.MarkerType,
                        ChartColor.ToHtml(marker.Color, true),
                        marker.DataSetIndex,
                        marker.DataPoint,
                        marker.Size,
                        i < Markers.Count - 1 ? "|" : String.Empty
                    );
                }

                cmd.Parameters.Add(new RestParameter("chm", buffer.ToString())); 
            }

            //
            // Range markers
            //
            if (RangeMarkers.Count > 0)
            {
                foreach (ChartRangeMarker marker in RangeMarkers)
                {
                    cmd.Parameters.Add(
                        new RestParameter("chm",
                            String.Format(
                                "r,{0},{1},{2},{3}",
                                ColorTranslator.ToHtml(marker.Color).TrimStart('#'),
                                0,
                                marker.Start,
                                marker.End
                            )
                        )
                    );
                }
            }

//            chbh=
//<bar width in pixels>,
//<optional space between bars in a group>,
//<optional space between groups>
            if (type.Equals(ChartType.BarVertical) || type.Equals(ChartType.BarVertical))
            {
                cmd.Parameters.Add(new RestParameter("chbh", "5,1,1"));  
            }

            cmd.Parameters.Add("chls", 
                String.Format(
                    "{0},{1},{2}", 
                    lineStyle.Thickness, 
                    lineStyle.SegmentLength,
                    lineStyle.BlankSegmentlength
               )
            );  

            Bitmap bitmap = null;

            HttpWebResponse response = cmd.Execute();

            using (Stream s = response.GetResponseStream())
            {
                bitmap = new Bitmap(s);
            }

            commandText = transport.CommandText;

            Debug.WriteLine(commandText);

            Console.WriteLine("Parameters: ");
  
            foreach (IRestParameter param in cmd.Parameters)
            {
                Console.WriteLine("{0} \t = \"{1}\"", param.Name, param.Value);
            }

            return bitmap;
        }

        /// <summary>
        /// Sacles the supplied values against the supplied axis range.
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Double[] ScaleValues(
            Double[] args,
            Double lowerBound,
            Double upperBound)
        {
            Double[] result = new Double[args.Length];

            Double temp;

            Double range = upperBound - lowerBound;

            for (Int32 i = 0; i < args.Length; i++)
            {
                result[i] = (Math.Round(ScaleValue(args[i], lowerBound, upperBound), 1));
            }

            return result;
        }

        /// <summary>
        /// Scales a single value, returning a value as a percentage of the range
        /// represented by <paramref name="upperBound"/> and <paramref name="lowerBound"/>.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="lowerBound"></param>
        /// <param name="upperBound"></param>
        /// <returns></returns>
        public static Double ScaleValue(Double value, Double lowerBound, Double upperBound)
        {
            Double range = Math.Abs(upperBound - lowerBound);

            return ((value - Math.Min(upperBound, lowerBound)) / range) * 100;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <param name="lowerBound"></param>
        /// <param name="upperBound"></param>
        /// <returns></returns>
        public static Double ScaleValueToPercentage(Double value, Double lowerBound, Double upperBound)
        {
            lowerBound = Math.Abs(lowerBound);
            upperBound = Math.Abs(upperBound);

            Double temp = value - Math.Min(lowerBound, upperBound);
            Double range = Math.Max(lowerBound, upperBound) - Math.Min(lowerBound, upperBound);

            return Math.Abs(Math.Round((temp / range), 2));
        }
    }
}
